//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/Resources/Texture.h"

namespace Engine
{
namespace Graphics
{

	struct gl_fbo_attach
	{
		enum type
		{
			COLOR			= gles::GL_COLOR_ATTACHMENT0,
			STENCIL			= gles::GL_STENCIL_ATTACHMENT,
			DEPTH			= gles::GL_DEPTH_ATTACHMENT,
			//DEPTH_STENCIL	= gles::GL_DEPTH_STENCIL_ATTACHMENT,
		};
	};


	class FrameBuffer;
	SHARED_POINTER( FrameBuffer );


	//
	// Frame Buffer
	//

	class FrameBuffer : public BaseObject
	{
	// variables
	private:
		ivec4			_viewport;
		gles::GLuint	_id;
		bool			_depth   : 1,
						_stencil : 1,
						_color   : 1;


	// methods
	private:
		void _OnAttach (gl_fbo_attach::type attachment, gles::GLuint id);

	public:
		FrameBuffer (const EngineSubSystemsRef ess);
		~FrameBuffer ();

		void Create ();
		void Destroy ();

		void Bind ();
		void Unbind ();
		bool IsBinded () const;

		void SetViewport (const ivec4 &vp);
		void ApplyViewport () const;
		void ClearBuffers () const;
		void ClearBuffers (const color4f &color, float depth = 1.0f, int stencil = 0) const;

		void Attach (const Texture &tex, gl_fbo_attach::type attachment, uint level = 0);
		void AttachMS (const Texture &tex, gl_fbo_attach::type attachment, uint samples, uint level = 0);
		void AttachFace (const Texture &tex, gl_fbo_attach::type attachment, gl_texture::type face, uint level = 0);
		void Detach (gl_fbo_attach::type attachment);
		void DetachAll ();

		bool CheckStatus ();

		bool HasColorTarget ()			const	{ return _color; }
		bool HasDepthTarget ()			const	{ return _depth; }
		bool HasStencilTarget ()		const	{ return _stencil; }

		gles::GLenum	Target ()		const	{ return gles::GL_FRAMEBUFFER; }
		gles::GLuint	Id ()			const	{ return _id; }

		const ivec4 &	GetViewport ()	const	{ return _viewport; }

		static FrameBufferPtr	New (const EngineSubSystemsRef ess);
	};


	

	// constructor
	inline FrameBuffer::FrameBuffer (const EngineSubSystemsRef ess) :
		BaseObject(ess), _id(0), _depth(0), _stencil(0), _color(0)
	{}


	// destructor
	inline FrameBuffer::~FrameBuffer ()
	{
		Destroy();
	}


	// Create
	inline void FrameBuffer::Create ()
	{
		using namespace gles;

		Destroy();
		GL_CALL( glGenFramebuffers( 1, &_id ) );

		ASSERT( _id != 0 );
	}


	// Destroy
	inline void FrameBuffer::Destroy ()
	{
		using namespace gles;

		if ( ESS()->GetGLStateManager()->GetFrameBuffer() == _id ) {
			Unbind();
		}

		if ( _id != 0 ) {
			GL_CALL( glDeleteFramebuffers( 1, &_id ) );
			_id = 0;
		}
		_viewport = ivec4();
		_depth    = 0;
		_stencil  = 0;
		_color    = 0;
	}


	// Bind
	inline void FrameBuffer::Bind ()
	{
		ASSERT( _id != 0 );
		ESS()->GetGLStateManager()->BindFrameBuffer( _id );
	}


	// Unbind
	inline void FrameBuffer::Unbind ()
	{
		ESS()->GetGLStateManager()->BindFrameBuffer( 0 );
	}
	

	// IsBinded
	inline bool FrameBuffer::IsBinded () const
	{
		return ESS()->GetGLStateManager()->GetFrameBuffer() == _id and _id != 0;
	}


	// SetViewport
	inline void FrameBuffer::SetViewport (const ivec4 &vp)
	{
		_viewport = vp;
	}
	

	// ApplyViewport
	inline void FrameBuffer::ApplyViewport () const
	{
		ESS()->GetGLStateManager()->Viewport( _viewport );
	}


	// ClearBuffers
	inline void FrameBuffer::ClearBuffers () const
	{
		ESS()->GetGLStateManager()->Clear( _color, _depth, _stencil );
	}
	

	// ClearBuffers
	inline void FrameBuffer::ClearBuffers (const color4f &color, float depth, int stencil) const
	{
		ESS()->GetGLStateManager()->ClearBuffer( color, _color, depth, _depth, stencil, _stencil );
	}


	// _OnAttach
	inline void FrameBuffer::_OnAttach (gl_fbo_attach::type attachment, gles::GLuint id)
	{
		ASSERT( IsBinded() );

		const bool	val = id != 0;

		switch ( attachment )
		{
			case gl_fbo_attach::DEPTH	:	_depth   = val;
			case gl_fbo_attach::STENCIL	:	_stencil = val;
			case gl_fbo_attach::COLOR	:	_color   = val;
		}
	}


	// Attach
	inline void FrameBuffer::Attach (const Texture &tex, gl_fbo_attach::type attachment, uint level)
	{
		using namespace gles;

		ASSERT( tex.Id() != 0 );
		ASSERT( tex.GetSampler().SupportRenderToTexture() );

		_OnAttach( attachment, tex.Id() );
		GL_CALL( glFramebufferTexture2D( Target(), attachment, tex.Target(), tex.Id(), level ) );
	}


	// AttachMS
	inline void FrameBuffer::AttachMS (const Texture &tex, gl_fbo_attach::type attachment, uint samples, uint level)
	{
		using namespace gles;

		if ( EXT_multisampled_render_to_texture::IsSupported() )
		{
			ASSERT( tex.Id() != 0 );
			ASSERT( tex.GetSampler().SupportRenderToTexture() );

			_OnAttach( attachment, tex.Id() );

			GL_CALL(
				EXT_multisampled_render_to_texture::
					glFramebufferTexture2DMultisample( Target(), attachment, tex.Target(), tex.Id(), level, samples ) );
		}
		else
		{
			WARNING( "multisample render to texture is not supported" );
		}
	}


	// Attach
	inline void FrameBuffer::AttachFace (const Texture &tex, gl_fbo_attach::type attachment, gl_texture::type face, uint level)
	{
		using namespace gles;
		
		ASSERT( tex.Id() != 0 );
		ASSERT( tex.GetSampler().SupportRenderToTexture() );

		_OnAttach( attachment, tex.Id() );
		GL_CALL( glFramebufferTexture2D( Target(), attachment, face, tex.Id(), level ) );
	}


	// Detach
	inline void FrameBuffer::Detach (gl_fbo_attach::type attachment)
	{
		using namespace gles;

		_OnAttach( attachment, 0 );
		GL_CALL( glFramebufferTexture2D( Target(), attachment, GL_TEXTURE_2D, 0, 0 ) );
	}


	// DetachAll
	inline void FrameBuffer::DetachAll ()
	{
		using namespace gles;
		
		ASSERT( IsBinded() );

		if ( _depth )	GL_CALL( glFramebufferTexture2D( Target(), GL_DEPTH_ATTACHMENT,   GL_TEXTURE_2D, 0, 0 ) );
		if ( _stencil )	GL_CALL( glFramebufferTexture2D( Target(), GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0 ) );
		if ( _color )	GL_CALL( glFramebufferTexture2D( Target(), GL_COLOR_ATTACHMENT0,  GL_TEXTURE_2D, 0, 0 ) );
	}
	

	// CheckStatus
	inline bool FrameBuffer::CheckStatus ()
	{
		using namespace gles;
		
		ASSERT( IsBinded() );

		GLenum  status;
		GL_CALL( status = glCheckFramebufferStatus ( Target() ) );
		
		switch ( status )
		{
			case GL_FRAMEBUFFER_COMPLETE :
				return true;

			case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
				RETURN_ERR( "Framebuffer incomplete: Attachment is not complete" );

			case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT :
				RETURN_ERR( "Framebuffer incomplete: No image is attached to FBO" );

			case GL_FRAMEBUFFER_UNSUPPORTED :
				RETURN_ERR( "Unsupported by FBO implementation" );

			case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS :
				RETURN_ERR( "Framebuffer incomplete dimensions" );
		};

		RETURN_ERR( "Framebuffer: Unknow error, code: 0x" << string().FormatI( status, 16 ) );
	}
	

	// New
	inline FrameBufferPtr FrameBuffer::New (const EngineSubSystemsRef ess)
	{
		return BaseObject::_New( new FrameBuffer( ess ) );
	}


}	// Graphics
}	// Engine